package cn.lg.soar.general.s3;

import cn.lg.soar.common.exceptions.BaseException;
import cn.lg.soar.common.util.FileTypeUtils;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.listener.SpringUtils;
import cn.lg.soar.general.entity.FileStorage;
import cn.lg.soar.general.service.FileAccessService;
import com.amazonaws.services.s3.model.*;
import lombok.SneakyThrows;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author luguoxiang 469599794@qq.com
 * @Date: 2024/3/1 17:25
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class FtpFileTemplateImpl implements FileTemplate {

    private final int configVersion;
    private final FTPClient client;
    private final String rootPath;

    public FtpFileTemplateImpl(FileStorage storage) {
        this.configVersion = storage.getVersion();

        FTPClient ftpClient = new FTPClient();
        // 保存FTP控制连接使用的字符集，必须在连接前设置
        ftpClient.setControlEncoding("UTF-8");
        // 2、指定服务器地址（端口），解析兼容路径形式：host:port/path、host/path、host:port、host
        String endpoint = storage.getEndpoint();
        String[] split = endpoint.split(":");
        if (split.length == 1) {
            split = new String[2];
            int i = endpoint.indexOf("/");
            if (i == -1) {
                split[0] = endpoint;
                split[1] = "";
            } else {
                split[0] = endpoint.substring(0, i);
                split[1] = endpoint.substring(i);
            }
        }
        String host = split[0];
        split = split[1].split("/");
        String port = split[0];
        try {
            if (DataUtil.isValuable(port)) {
                ftpClient.connect(host, Integer.parseInt(port));
            } else {
                ftpClient.connect(host);
            }
            // 3、指定账号和密码
            ftpClient.login(storage.getAccessKey(), storage.getSecretKey());

            // 连接成功或者失败返回的状态码
            int reply = ftpClient.getReplyCode();
            // 如果reply返回230表示成功，如果返回530表示无密码或用户名错误或密码错误或用户权限问题。
            if (!FTPReply.isPositiveCompletion(reply)) {
                ftpClient.disconnect();
                throw new BaseException("C0001", "连接FTP失败：" + reply);
            }

            // 创建目录
            StringBuilder sb = new StringBuilder("/");
            for (int i = 1; i < split.length; i++) {
                sb.append(split[i]).append("/");
                // 判断目录是否已经存在
                String path = sb.toString();
                if (!ftpClient.changeWorkingDirectory(path)) {
                    // 创建目录 一次只能创建一个目录
                    ftpClient.makeDirectory(path);
                }
            }
            this.rootPath = sb.toString();
            this.client = ftpClient;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    @SneakyThrows
    public void createBucket(String bucketName) {
        // 判断目录是否已经存在
        String path = getBucketPath(bucketName);
        if (!client.changeWorkingDirectory(path)) {
            // 创建目录 一次只能创建一个目录
            client.makeDirectory(path);
        }
    }

    @Override
    @SneakyThrows
    public List<Bucket> listBuckets() {
        // 切换到根目录
        FTPFile[] files = client.listDirectories(this.rootPath);
        if (DataUtil.isEmpty(files)) {
            return Collections.emptyList();
        }
        List<Bucket> list = new ArrayList<>(files.length);
        for (FTPFile file : files) {
            if (file.isDirectory()) {
                Bucket bucket = new Bucket();
                bucket.setName(file.getName());
//                bucket.setCreationDate(new Date(file.lastModified()));
                list.add(bucket);
            }
        }
        return list;
    }

    @Override
    @SneakyThrows
    public void removeBucket(String bucketName) {
        client.removeDirectory(getBucketPath(bucketName));
    }

    @Override
    public void upload(String bucketName, String filename, InputStream stream) {
        String path = getFilePath(bucketName, filename);
        try {
            // 6、指定上传方式为二进制方式
            client.setFileType(FTP.BINARY_FILE_TYPE);
            // 7、remote指定上传远程服务器的文件名 local指本地的输入流
            client.storeFile(path, stream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    @SneakyThrows
    public String getUrl(String bucketName, String filename, Integer expires) {
        FileAccessService fileAccessService = SpringUtils.getBean(FileAccessService.class);
        return fileAccessService.createTemporaryUrl(true, Long.parseLong(filename.split("\\.")[0]), expires);
    }

    @Override
    public String getUrl(String bucketName, String filename) {
        FileAccessService fileAccessService = SpringUtils.getBean(FileAccessService.class);
        return fileAccessService.createFixedSecretUrl(Long.parseLong(filename.split("\\.")[0]));
    }

    @Override
    public void removeFile(String bucketName, String filename) {
        String path = getFilePath(bucketName, filename);
        try {
            client.deleteFile(path);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    @SneakyThrows
    public S3Object getObject(String bucketName, String filename) {
        try {
            InputStream inputStream = client.retrieveFileStream(getFilePath(bucketName, filename));
            if (inputStream == null) {
                throw new AmazonS3Exception("The specified key does not exist.");
            }
            client.completePendingCommand();
            S3Object object = new S3Object();
            object.setBucketName(bucketName);
            object.setObjectContent(inputStream);
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(FileTypeUtils.getContentType(filename));
            object.setObjectMetadata(metadata);
            return object;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    @SneakyThrows
    public List<S3ObjectSummary> listObjects(String bucketName, String prefix, boolean recursive) {
        String path = getBucketPath(bucketName);
        try {
            FTPFile[] files = client.listFiles(path);
            if (DataUtil.isEmpty(files)) {
                return Collections.emptyList();
            }
            List<S3ObjectSummary> list = new ArrayList<>(files.length);
            for (FTPFile file : files) {
                if (file.isFile() && file.getName().startsWith(prefix)) {
                    S3ObjectSummary object = new S3ObjectSummary();
                    object.setBucketName(bucketName);
                    object.setSize(file.getSize());
                    list.add(object);
                }
            }
            return list;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean isExist(String bucketName, String filename) {
        try {
            return client.getSize(getFilePath(bucketName, filename)) != null;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int getConfigVersion() {
        return configVersion;
    }

    @Override
    public void test() {
        int replyCode = client.getReplyCode();
        if (FTPReply.isPositiveCompletion(replyCode)) {
            return;
        }
        throw new BaseException("A0404", "FTP连接失败");
    }

    @Override
    public void disconnect() {
        try {
            client.disconnect();
        } catch (Exception e) {
            LOGGER.error("关闭FTP连接失败", e);
        }
    }

    private String getBucketPath(String bucketName) {
        return rootPath + "/" + bucketName;
    }

    private String getFilePath(String bucketName, String filename) {
        return getBucketPath(bucketName) + "/" + filename;
    }

}